package webserver.standard;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.jgroups.Address;
import org.jgroups.ChannelClosedException;
import org.jgroups.ChannelException;
import org.jgroups.ChannelNotConnectedException;
import org.jgroups.JChannel;
import org.jgroups.MembershipListener;
import org.jgroups.Message;
import org.jgroups.MessageListener;
import org.jgroups.View;
import org.jgroups.blocks.PullPushAdapter;
import org.jgroups.blocks.RpcDispatcher;
import org.jgroups.util.Util;

public class WebServer implements Server, MessageListener, MembershipListener
{
	HandlerFactory defaultHandler;
	HashMap<String, HandlerFactory> handlers;
	String handlerSeperation = "";
	ServerSocket listenSocket;
	int numberOfObjects = -1;
	HashMap<Integer, Object> serverObjects;
	private JChannel channel;

	public WebServer(int port, HandlerFactory defaultHandler,
			String handlerSeperation) throws IOException
	{
		listenSocket = new ServerSocket(port);
		handlers = new HashMap<String, HandlerFactory>();
		serverObjects = new HashMap<Integer, Object>();
		this.defaultHandler = defaultHandler;
		this.handlerSeperation = handlerSeperation;
	}

	@Override
	public synchronized int addObject(Object object)
	{
		if (serverObjects.containsValue(object))
		{
			return getObjects().indexOf(object);
		}
		else
		{
			numberOfObjects++;
			Integer num = new Integer(numberOfObjects);
			serverObjects.put(new Integer(num), object);
			return numberOfObjects;
		}
	}

	public void attachHandlers(String prefix, HandlerFactory handler)
	{
		handlers.put(prefix, handler);
	}

	public void forwardRequest() throws IOException
	{
		while (true)
		{
			HandlerFactory handlerf;
			// .accept() will block this thread until a connection is
			// established
			// then a thread will be created, and then this thread waits all
			// over again
			Socket socket = listenSocket.accept();
			String firstLine = new BufferedReader(new InputStreamReader(socket
					.getInputStream())).readLine();
			System.out.println("Request at server: " + firstLine);
			if (firstLine != null)
			{
				if (firstLine.lastIndexOf(handlerSeperation) == -1)
				{
					handlerf = defaultHandler;
				}
				if (firstLine.startsWith("GET"))
				{
					handlerf = handlers.get(firstLine.subSequence(4, firstLine
							.indexOf(handlerSeperation)));
				}
				else if (firstLine.startsWith("PUT"))
				{
					handlerf = handlers.get(firstLine.subSequence(4, firstLine
							.indexOf(handlerSeperation)));
				}
				else if (firstLine.startsWith("POST"))
				{
					handlerf = handlers.get(firstLine.subSequence(5, firstLine
							.indexOf(handlerSeperation)));
				}
				else if (firstLine.startsWith("HEAD"))
				{
					handlerf = handlers.get(firstLine.subSequence(5, firstLine
							.indexOf(handlerSeperation)));
				}
				else
				{
					handlerf = defaultHandler;
				}
				if (handlerf == null)
				{
					handlerf = defaultHandler;
				}
				if (channel != null)
				{
					if (channel.isConnected() && channel.isOpen())
					{
						try
						{
							channel.send(new Message(null, null, firstLine));
						}
						catch (ChannelNotConnectedException e)
						{
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						catch (ChannelClosedException e)
						{
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				new Thread(handlerf.getHandler(this, firstLine, socket))
						.start();
			}
			else
				socket.getOutputStream().write(
						("HTTP 1/0" + " " + "400 Bad Request").getBytes());
		}
	}

	@Override
	public synchronized Object getObject(int objectNumber)
	{
		return serverObjects.get(new Integer(objectNumber));
	}

	private List<Object> getObjects()
	{
		ArrayList<Object> returnObjects = new ArrayList<Object>(numberOfObjects);
		for (int i = 0; i <= numberOfObjects; i++)
		{
			returnObjects.add(i, serverObjects.get(i));
		}
		return returnObjects;
	}

	@Override
	public void setDefaultHandlerFactory(HandlerFactory hf)
	{
		this.defaultHandler = hf;
	}

	@Override
	public void setHandlerSeperation(String seperator)
	{
		this.handlerSeperation = seperator;
	}

	@SuppressWarnings("deprecation")
	public void joinJGroupChannel(String channelProperties, String channelName)
	{
		try
		{
			channel = new JChannel(channelProperties);
			channel.connect(channelName);
			PullPushAdapter adapter = new PullPushAdapter(channel);
			adapter.setListener(this);
			adapter.addMembershipListener(this);
		}
		catch (ChannelException e)
		{
			e.printStackTrace();
		}
	}

	@Override
	public byte[] getState()
	{
		try
		{
			return Util.objectToByteBuffer(serverObjects);
		}
		catch (Exception e)
		{
			// TODO Auto-generated catch block
			return null;
		}
	}

	@Override
	public void receive(Message message)
	{
		Object[] parts = (Object[]) message.getObject();
		String firstLine = (String) parts[0];
		Thread handler = new Thread(defaultHandler.getHandler(this, firstLine));
		handler.start();

		try
		{
			handler.join();
		}
		catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setState(byte[] arg0)
	{
		try
		{
			serverObjects = (HashMap<Integer, Object>) Util
					.objectFromByteBuffer(arg0);
		}
		catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void block()
	{
		System.out.println("block message sends");
	}

	@Override
	public void suspect(Address arg0)
	{
		System.out.println("Member suspected faulty: " + arg0);
	}

	@Override
	public void viewAccepted(View arg0)
	{
		System.out.println("new view: " + arg0);
	}
}